package cn.csc.module.pay.service.order;

import cn.csc.framework.common.pojo.PageResult;
import cn.csc.framework.common.util.json.JsonUtils;
import cn.csc.framework.common.util.number.MoneyUtils;
import cn.csc.framework.jzb.core.JzbClient;
import cn.csc.framework.pay.core.client.PayClient;
import cn.csc.framework.pay.core.client.PayCommonResult;
import cn.csc.framework.pay.core.client.dto.PayNotifyReqDTO;
import cn.csc.framework.pay.core.client.dto.PayOrderNotifyRespDTO;
import cn.csc.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
import cn.csc.framework.pay.core.client.dto.PayOrderUnifiedRespDTO;
import cn.csc.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.csc.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.csc.module.pay.controller.admin.order.vo.*;
import cn.csc.module.pay.controller.app.order.vo.AppPayOrderOuterSubmitReqVO;
import cn.csc.module.pay.convert.order.PayOrderConvert;
import cn.csc.module.pay.dal.dataobject.jzborder.JzbOrderDO;
import cn.csc.module.pay.dal.dataobject.jzbtask.PayJzbTaskDO;
import cn.csc.module.pay.dal.dataobject.merchant.PayAppDO;
import cn.csc.module.pay.dal.dataobject.merchant.PayChannelDO;
import cn.csc.module.pay.dal.dataobject.mqrecord.PayMqRecordDO;
import cn.csc.module.pay.dal.dataobject.order.PayOrderDO;
import cn.csc.module.pay.dal.dataobject.order.PayOrderExtensionDO;
import cn.csc.module.pay.dal.dataobject.rate.PayJzbRateDO;
import cn.csc.module.pay.dal.mysql.jzborder.JzbOrderMapper;
import cn.csc.module.pay.dal.mysql.jzbtask.PayJzbTaskMapper;
import cn.csc.module.pay.dal.mysql.mqrecord.PayMqRecordMapper;
import cn.csc.module.pay.dal.mysql.order.PayOrderExtensionMapper;
import cn.csc.module.pay.dal.mysql.order.PayOrderMapper;
import cn.csc.module.pay.dal.mysql.rate.PayJzbRateMapper;
import cn.csc.module.pay.enums.ErrorCodeConstants;
import cn.csc.module.pay.enums.notify.PayNotifyTypeEnum;
import cn.csc.module.pay.enums.order.PayOrderNotifyStatusEnum;
import cn.csc.module.pay.enums.order.PayOrderStatusEnum;
import cn.csc.module.pay.enums.refund.PayRefundTypeEnum;
import cn.csc.module.pay.framework.pay.config.PayProperties;
import cn.csc.module.pay.message.JzbOrderMessage;
import cn.csc.module.pay.mq.producer.JzbOrderProducer;
import cn.csc.module.pay.mq.producer.PayOrderNotifyProducer;
import cn.csc.module.pay.service.merchant.PayAppService;
import cn.csc.module.pay.service.merchant.PayChannelService;
import cn.csc.module.pay.service.notify.PayNotifyService;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

import static cn.csc.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.csc.framework.common.util.json.JsonUtils.toJsonString;
import static cn.csc.module.pay.enums.ErrorCodeConstants.*;
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_PATTERN;

/**
 * 支付订单 Service 实现类
 *
 * @author aquan
 */
@Service
@Validated
@Slf4j
public class PayOrderServiceImpl implements PayOrderService {

    @Resource
    private PayProperties payProperties;

    @Resource
    private PayOrderMapper orderMapper;
    @Resource
    private PayOrderExtensionMapper orderExtensionMapper;

    @Resource
    private PayAppService appService;
    @Resource
    private PayChannelService channelService;
    @Resource
    private PayNotifyService notifyService;
    @Resource
    private JzbOrderMapper jzbOrderMapper;
    @Resource
    private PayJzbRateMapper payJzbRateMapper;

    @Resource
    private PayJzbTaskMapper payJzbTaskMapper;

    @Resource
    private PayMqRecordMapper payMqRecordMapper;

    @Resource
    private PayOrderNotifyProducer payOrderNotifyProducer;

    @Resource
    private JzbOrderProducer jzbOrderProducer;

    @Resource
    private JzbClient jzbClient;

    @Value("${spring.profiles.active}")
    private String profile;

    @Value("${csc.pay.enableTest:false}")
    private Boolean enableTest;

    @Value("${csc.pay.payAmt:1}")
    private Integer payAmt;

    @Resource
    private TransactionTemplate transactionTemplate;

    @Override
    public PayOrderDO getOrder(BigInteger id) {
        return orderMapper.selectById(id);
    }

    @Override
    public PayOrderDO getOrder(BigInteger appId, String merchantOrderId) {
        return orderMapper.selectByAppIdAndMerchantOrderId(appId, merchantOrderId);
    }

    @Override
    public PageResult<PayOrderDO> getOrderPage(PayOrderPageReqVO pageReqVO) {
        return orderMapper.selectPage(pageReqVO);
    }

    @Override
    public List<PayOrderDO> getOrderList(PayOrderExportReqVO exportReqVO) {
        return orderMapper.selectList(exportReqVO);
    }

    // TODO @艿艿：需要优化。不确定这个方法的作用
    @Override
    public List<PayOrderDO> getOrderSubjectList(Collection<BigInteger> idList) {
        return orderMapper.findByIdListQueryOrderSubject(idList);
    }

    /**
     * 创建支付单
     *
     * @param reqDTO 创建请求
     * @return 支付单编号
     */
    @Override
    public BigInteger createPayOrder(PayOrderCreateReqDTO reqDTO) {
        // 校验 App
        PayAppDO app = appService.validPayApp(reqDTO.getAppId());

        // 测试
        if (enableTest){
            reqDTO.setAmount(reqDTO.getAmount() == 0 ? 0 : payAmt);
        }

        // 查询对应的支付交易单是否已经存在。如果是，则直接返回
        PayOrderDO order = orderMapper.selectByAppIdAndMerchantOrderId(
                reqDTO.getAppId(), reqDTO.getMerchantOrderId());
        if (order != null) {
            log.warn("[createPayOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
                    order.getMerchantOrderId(), toJsonString(order)); // 理论来说，不会出现这个情况
            return order.getId();
        }

        // 创建支付交易单
        order = PayOrderConvert.INSTANCE.convert(reqDTO)
                .setMerchantId(app.getMerchantId()).setAppId(app.getId());
        // 商户相关字段
        order.setNotifyUrl(app.getPayNotifyUrl())
                .setNotifyStatus(PayOrderNotifyStatusEnum.NO.getStatus());
        // 订单相关字段
        order.setStatus(PayOrderStatusEnum.WAITING.getStatus());
        // 退款相关字段
        order.setRefundStatus(PayRefundTypeEnum.NO.getStatus())
                .setRefundTimes(0).setRefundAmount(0L);
        orderMapper.insert(order);
        // 最终返回
        return order.getId();
    }

    /**
     * 提交支付, 支付订单已经创建
     * 此时，会发起支付渠道的调用
     *
     * @param reqVO  提交请求
     * @param userIp 提交 IP
     * @return 提交结果
     */
    @Override
    public PayOrderSubmitRespVO submitPayOrder(PayOrderSubmitReqVO reqVO, String userIp) {
        log.info("submitPayOrder: {}, {}", reqVO, userIp);
        // 1. 获得 PayOrderDO ，并校验其是否存在
        PayOrderDO order = validatePayOrderCanSubmit(reqVO.getId());
        // 1.2 校验支付渠道是否有效
        PayChannelDO channel = validatePayChannelCanSubmit(order.getAppId(), order.getMerchantId(), reqVO.getChannelCode());
        PayClient client = channelService.getPayClient(channel.getId());

        // 2. 插入 PayOrderExtensionDO
        String no = payProperties.getOrderNoPrefix() + IdUtil.getSnowflakeNextId();
        PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
                .setOrderId(order.getId()).setNo(no)
                .setChannelId(channel.getId()).setChannelCode(channel.getCode())
                .setStatus(PayOrderStatusEnum.WAITING.getStatus());
        orderExtensionMapper.insert(orderExtension);
        order.setSuccessExtensionId(orderExtension.getId());
        order.setChannelId(channel.getId());
        order.setChannelCode(reqVO.getChannelCode());
        order.setNotifyUrl(genChannelPayNotifyUrl(channel));
        order.setNo(no);
        orderMapper.updateById(order);

        log.info("submitPayOrder order: {}", order);
        // 实付金额为0，直接通知支付完成
        if(order.getAmount() == 0){
            // 支付状态更新为成功，sendPayOrderNotifyMessage发送的为事务消息，会查询支付状态
            orderMapper.updateStatusById(order.getId(), 10);
            log.info("submitPayOrder merchantOrderId: {}", order.getMerchantOrderId());
            Boolean message = payOrderNotifyProducer.sendPayOrderNotifyMessage(order.getMerchantOrderId());
            log.info("submitPayOrder message: {}", message);
        }

        // 3. 调用三方接口
        PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO)
                .setUserIp(userIp)
                // 商户相关的字段
                .setMerchantOrderId(orderExtension.getNo()) // 注意，此处使用的是 PayOrderExtensionDO.no 属性！
                .setSubject(order.getSubject())
                .setBody(order.getBody()+"\\"+no)  // 备注加商户订单号
                .setNotifyUrl(genChannelPayNotifyUrl(channel))
                .setReturnUrl(genChannelReturnUrl(channel))
                // 订单相关字段
                .setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
        PayOrderUnifiedRespDTO unifiedOrderRespDTO = client.unifiedBeanOrder(unifiedOrderReqDTO);
        log.info("submitPayOrder支付结果：{}", unifiedOrderRespDTO);

        // TODO 轮询三方接口，是否已经支付的任务
        // 返回成功
        return PayOrderConvert.INSTANCE.convert(unifiedOrderRespDTO);
    }

    @Override
    public PayOrderSubmitRespVO submitPayOrderOuter(AppPayOrderOuterSubmitReqVO reqVO){
        log.info("submitPayOrderOuter 请求参数: {}", reqVO);
        // 1. 获得 PayOrderDO ，并校验其是否存在
        PayOrderDO order = validatePayOrderCanSubmit(reqVO.getMerchantOrderId());
        // 1.2 校验支付渠道是否有效
        PayChannelDO channel = validatePayChannelCanSubmit(order.getAppId(), order.getMerchantId(), reqVO.getChannelCode());
        PayClient client = channelService.getPayClient(channel.getId());

        // 2. 插入 PayOrderExtensionDO
        String no = payProperties.getOrderNoPrefix() + IdUtil.getSnowflakeNextId();
        PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO)
                .setOrderId(order.getId()).setNo(no)
                .setChannelId(channel.getId()).setChannelCode(channel.getCode())
                .setStatus(PayOrderStatusEnum.WAITING.getStatus());
        orderExtensionMapper.insert(orderExtension);
        order.setSuccessExtensionId(orderExtension.getId());
        order.setChannelId(channel.getId());
        order.setChannelCode(reqVO.getChannelCode());
        order.setNo(no);
        orderMapper.updateById(order);

        // 3. 调用三方接口
        PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO)
                // 商户相关的字段
                .setMerchantOrderId(orderExtension.getNo()) // 注意，此处使用的是 PayOrderExtensionDO.no 属性！
                .setSubject(order.getSubject())
                .setBody(order.getBody()+"\\"+no)  // 备注加商户订单号
                .setNotifyUrl(genChannelPayNotifyUrl(channel))
                .setReturnUrl(genChannelReturnUrl(channel))
                // 订单相关字段
                .setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
        PayOrderUnifiedRespDTO unifiedOrderRespDTO = client.unifiedBeanOrder(unifiedOrderReqDTO);
        log.debug("submitPayOrderOuter支付结果：{}", unifiedOrderRespDTO);

        // 返回成功
        return PayOrderConvert.INSTANCE.convert(unifiedOrderRespDTO);
    }


    /**
     * 提交支付（创建支付订单和提交支付合并）
     * 此时，会发起支付渠道的调用
     *
     * @param reqVO  提交请求
     * @param userIp 提交 IP
     * @return 提交结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public PayCommonResult<PayOrderUnifiedRespDTO> submitCommonPayOrder(PayOrderSubmitCommReqVO reqVO, String userIp) {

        PayOrderDO order = PayOrderConvert.INSTANCE.convert(reqVO);
        // 校验appId和channelCode
        PayChannelDO channel = validatePayChannelCanSubmit(order.getAppId(), reqVO.getMerchantId(), reqVO.getChannelCode());
        order.setMerchantId(channel.getMerchantId());
        order.setChannelId(channel.getId());
        order.setStatus(PayOrderStatusEnum.WAITING.getStatus());
        LocalDateTime createTime = LocalDateTimeUtil.now();
        LocalDateTime expireTime = LocalDateTimeUtil.offset(createTime, 30, ChronoUnit.MINUTES);
        order.setCreateTime(createTime);
        order.setExpireTime(expireTime);
        order.setRefundStatus(PayRefundTypeEnum.NO.getStatus());
        order.setUpdateTime(createTime);
        order.setNotifyStatus(PayOrderNotifyStatusEnum.NO.getStatus());
        order.setRefundTimes(0);
        order.setRefundAmount(0l);
        orderMapper.insert(order);
        PayClient client = channelService.getPayClient(channel.getId());
        // 2. 插入 PayOrderExtensionDO
        String no = payProperties.getOrderNoPrefix() + IdUtil.getSnowflakeNextId();
        PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
                .setOrderId(order.getId()).setNo(no)//generateOrderExtensionNo()
                .setChannelId(channel.getId()).setChannelCode(channel.getCode())
                .setStatus(PayOrderStatusEnum.WAITING.getStatus());
        orderExtensionMapper.insert(orderExtension);
        order.setSuccessExtensionId(orderExtension.getId());
        order.setNo(no);
        orderMapper.updateById(order);

        // 3. 调用三方接口
        PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO)
                // 商户相关的字段
                .setMerchantOrderId(orderExtension.getNo()) // 注意，此处使用的是 PayOrderExtensionDO.no 属性！
                .setSubject(order.getSubject())
                .setRemark(reqVO.getRemark()+"\\"+no)  // 备注加商户订单号
                .setNotifyUrl(genChannelApiPayNotifyUrl(channel))
                .setReturnUrl(genChannelApiReturnUrl(channel))
                // 订单相关字段
                .setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
        log.info("[PayOrderServiceImpl.submitCommonPayOrder]支付回调本地地址:{}", unifiedOrderReqDTO.getNotifyUrl());

        PayOrderUnifiedRespDTO respDTO = client.unifiedBeanOrder(unifiedOrderReqDTO);
        log.debug("submitCommonPayOrder支付结果：{}", respDTO);
        // 4. 如果调用直接支付成功，则直接更新支付单状态为成功。例如说：付款码支付，免密支付时，就直接验证支付成功
        // 如有渠道错误码，则抛出业务异常，提示用户
        if (respDTO != null && StrUtil.isNotEmpty(respDTO.getChannelErrorCode())) {
            if ("FAIL".equals(respDTO.getChannelErrorCode())) {
                // 通联返回{“retcode”："FAIL", "retmsg":"系统繁忙"}，重试
                try {
                    TimeUnit.SECONDS.sleep(5);
                    respDTO = client.unifiedBeanOrder(unifiedOrderReqDTO);
                    if (respDTO != null && StrUtil.isNotEmpty(respDTO.getChannelErrorCode())) {
                        throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, respDTO.getChannelErrorCode(),
                                respDTO.getChannelErrorMsg());
                    }
                    return PayCommonResult.success(respDTO);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, respDTO.getChannelErrorCode(),
                    respDTO.getChannelErrorMsg());
        }


        // TODO 轮询三方接口，是否已经支付的任务
        // 返回成功
        return PayCommonResult.success(respDTO);
    }

    private PayOrderDO validatePayOrderCanSubmit(BigInteger id) {
        PayOrderDO order = orderMapper.selectById(id);
        if (order == null) { // 是否存在
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态，必须是待支付
            throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
        }
        return order;
    }

    private PayOrderDO validatePayOrderCanSubmit(String merchantOrderId) {
        PayOrderDO order = orderMapper.selectByMerchantOrderId(merchantOrderId);
        if (order == null) { // 是否存在
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态，必须是待支付
            throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
        }
        return order;
    }


    private PayChannelDO validatePayChannelCanSubmit(BigInteger appId, BigInteger merchantId, String channelCode) {
        // 校验 App
        appService.validPayApp(appId);

        // 校验支付渠道是否有效
        PayChannelDO channel = channelService.validPayChannel(appId, merchantId, channelCode);
        // 校验支付客户端是否正确初始化
        PayClient client = channelService.getPayClient(channel.getId());
        if (client == null) {
            log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
            throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
        }
        return channel;
    }

    /**
     * 根据支付渠道的编码，生成支付渠道的返回地址
     *
     * @param channel 支付渠道
     * @return 支付成功返回的地址。 配置地址 + "/" + channel id
     */
    private String genChannelReturnUrl(PayChannelDO channel) {
        return payProperties.getReturnUrl() + "/" + channel.getId();
    }

    private String genChannelApiReturnUrl(PayChannelDO channel) {
        return payProperties.getApiReturnUrl() + "/" + channel.getId();
    }

    /**
     * 根据支付渠道的编码，生成支付渠道的回调地址
     *
     * @param channel 支付渠道
     * @return 支付渠道的回调地址  配置地址 + "/" + channel id
     */
    private String genChannelPayNotifyUrl(PayChannelDO channel) {
        return payProperties.getCallbackUrl() + "/" + channel.getId();
    }

    private String genChannelApiPayNotifyUrl(PayChannelDO channel) {
        return payProperties.getApiCallbackUrl() + "/" + channel.getId();
    }

    @Override
    public Boolean notifyOrder(BigInteger channelId, PayOrderUnifiedRespDTO notify) {
        // 校验支付渠道是否有效
        PayChannelDO channel = channelService.validPayChannel(channelId);
        // 更新支付订单为已支付
        return getSelf().notifyOrder(channel, notify);
    }

    /**
     * 通知并更新订单的支付结果
     *
     * @param channel 支付渠道
     * @param notify  通知
     */
    @Transactional(rollbackFor = Exception.class)
    // 注意，如果是方法内调用该方法，需要通过 getSelf().notifyPayOrder(channel, notify) 调用，否则事务不生效
    public Boolean notifyOrder(PayChannelDO channel, PayOrderUnifiedRespDTO notify) {
        // 情况一：支付成功的回调
        if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) {
            return notifyOrderSuccess(channel, notify);
        }
        // 情况二：支付失败的回调
        if (PayOrderStatusRespEnum.isClosed(notify.getStatus())) {
            notifyOrderClosed(channel, notify);
        }
        return true;
        // 情况三：WAITING：无需处理
        // 情况四：REFUND：通过退款回调处理
    }

    private Boolean notifyOrderSuccess(PayChannelDO channel, PayOrderUnifiedRespDTO notify) {
        // 1. 更新 PayOrderExtensionDO 支付成功
        PayOrderExtensionDO orderExtension = updateOrderSuccess(notify);
        PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
        // 2. 更新 PayOrderDO 支付成功
        Boolean paid = updateOrderSuccess(channel, orderExtension, notify);
        // 内部订单
        if (order.getOrderType() == 7){
            // 支付成功，发送支付成功mq消息
            log.info("notifyOrderSuccess mq start: {}",order.getId());
            payOrderNotifyProducer.sendPayOrderNotifyMessage(order.getMerchantOrderId());
            log.info("notifyOrderSuccess mq end: {}",order.getId());
        }

        if (paid) { // 如果之前已经成功回调，则直接返回，不用重复记录支付通知记录；例如说：支付平台重复回调
            if (order.getOrderType() >= 5){
                JzbOrderDO jzbOrderDO = jzbOrderMapper.selectByPayOrderId(order.getId());
                if (jzbOrderDO != null){
                    return true;
                }
            } else {
                return true;
            }
        }

        // 见证宝冻结充值订单
        if (order.getOrderType() >= 5) {
            // 当前方法的事务提交成功后再执行
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    try {
                        log.info("[----进入在线充值,第三方订单号:{}---]", order.getMerchantOrderId());
                        // 可能多次支付回调
                        JzbOrderDO orderDO = jzbOrderMapper.selectByPayOrderId(order.getId());
                        // 首次支付回调，创建见证宝订单，向rocketmq发送消息
                        if (orderDO == null) {
                            // 字典类型, 1:商户费率,2:分账费率
                            PayJzbRateDO rateDO = payJzbRateMapper.selectByDicData(1, order.getMerchantId().longValue(), order.getAppUserId());
                            if (rateDO == null) {
                                // 查询平台商户的默认手续费率,  `rate`为decimal(10,6), 参与计算之后 再 精确保留2位小数
                                rateDO = payJzbRateMapper.selectDefault(order.getMerchantId().longValue());
                            }

                            // 商户费率
                            BigDecimal handRate = Optional.ofNullable(rateDO)
                                    .map(rt -> new BigDecimal(rt.getDicValue()))
                                    .orElse(new BigDecimal("0.001"));  // 默认手续费率为0.001

                            BigDecimal dec = new BigDecimal(order.getAmount());
                            BigDecimal money = new BigDecimal(0);
                            money = money.add(dec.divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP));
                            //计算通联配置的费率金额
                            BigDecimal formality = money.multiply(new BigDecimal(channel.getFeeRate())).setScale(2,BigDecimal.ROUND_HALF_UP);
                            JzbOrderDO jzbOrderDO = new JzbOrderDO();
                            jzbOrderDO.setJzbFreezeStatus(0);
                            jzbOrderDO.setAmount(money);
                            jzbOrderDO.setPayOrderId(order.getId());
                            jzbOrderDO.setTradeNo(notify.getTradeNo());
                            jzbOrderDO.setTradeStatus(1);
                            jzbOrderDO.setTradeType(6);
                            jzbOrderDO.setMerchantId(order.getMerchantId());
                            jzbOrderDO.setAppUserId(order.getAppUserId());
                            jzbOrderDO.setFormality(formality);//通联收取的金额
                            // 华正支付商户费率
                            jzbOrderDO.setHandFeeRate(handRate.toPlainString());
                            // 外部订单
                            if (order.getOrderType() == 5){
                                // 判断是否为活动商品的订单
                                String[] bArr = order.getBody().split(";");
                                if (bArr.length == 2) {
                                    log.info("notifyPayOrder: {},{}", bArr[1], rateDO);
                                    PayJzbRateDO dicData = payJzbRateMapper.selectByDicData(2, order.getMerchantId().longValue(), bArr[1]);
                                    jzbOrderDO.setDivideRate(dicData.getDicValue());
                                } else if (bArr.length > 2) {
                                    String[] array = Stream.of(bArr).skip(1).toArray(String[]::new);
                                    // 订单中包含多个活动的商品
                                    log.info("notifyPayOrder: {},{}", array, rateDO);
                                    PayJzbRateDO dicData = payJzbRateMapper.selectMaxRateByDicData(2, order.getMerchantId().longValue(), array);
                                    jzbOrderDO.setDivideRate(dicData.getDicValue());
                                }
                            }

                            String seq = jzbClient.getCnsmrSeqNo();
                            jzbOrderDO.setFreezeRechargeNo(seq);
                            jzbOrderMapper.insert(jzbOrderDO);

                            JzbOrderMessage jzbOrderMessage = new JzbOrderMessage(jzbOrderDO.getId(), seq, "1");

                            // 日志流水表
                            PayMqRecordDO recordDO = new PayMqRecordDO();
                            recordDO.setBusiType("1");  // 冻结充值
                            recordDO.setOrderId(order.getId());
                            recordDO.setJzbOrderId(jzbOrderDO.getId());
                            recordDO.setChannel("jzbOrder-out-0");
                            recordDO.setMessage(JsonUtils.toJsonString(jzbOrderMessage));
                            recordDO.setDeliverStatus("0");
                            // 业务流水表，保障消息投递，防止重复消费
                            payMqRecordMapper.insert(recordDO);

                            // 事务消息
                            jzbOrderProducer.sendJzbOrderMessage(jzbOrderMessage);
                            recordDO.setDeliverStatus("1");
                            payMqRecordMapper.updateById(recordDO);

                        }
                    } catch (Exception e) {
                        // TODO 处理异常情况，没有生成见证宝订单
                        PayJzbTaskDO builder = PayJzbTaskDO.builder().busiType("3").orderType(order.getOrderType()).orderId(order.getId())
                                .appUserId(order.getAppUserId()).taskStatus(0).taskTime(LocalDate.now()).build();
                        payJzbTaskMapper.insert(builder);
                        e.printStackTrace();
                        log.error("[冻结充值发送mq消息失败{}]", e.getMessage());
                    }
                }
            });
        }

        // 3. 插入支付通知记录
        if(order.getOrderType() != 7){  // 内部订单不需要再做通知
            notifyService.createPayNotifyTask(PayNotifyTypeEnum.ORDER.getType(),
                    orderExtension.getOrderId(), notify.getTradeNo(), notify.getSuccessTime());
        }
        return true;
    }

    /**
     * 更新 PayOrderExtensionDO 支付成功
     *
     * @param notify 通知
     * @return PayOrderExtensionDO 对象
     */
    private PayOrderExtensionDO updateOrderSuccess(PayOrderUnifiedRespDTO notify) {
        // 1. 查询 PayOrderExtensionDO
        PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getTradeNo());
        if (orderExtension == null) {
            throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
        }
        if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { // 如果已经是成功，直接返回，不用重复更新
            log.info("[updateOrderExtensionSuccess][orderExtension({}) 已经是已支付，无需更新]", orderExtension.getId());
            return orderExtension;
        }
        if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
        }

        // 2. 更新 PayOrderExtensionDO
        int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(),
                PayOrderExtensionDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(toJsonString(notify)).build());
        if (updateCounts == 0) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
        }
        log.info("[updateOrderExtensionSuccess][orderExtension({}) 更新为已支付]", orderExtension.getId());
        return orderExtension;
    }

    /**
     * 更新 PayOrderDO 支付成功
     *
     * @param channel        支付渠道
     * @param orderExtension 支付拓展单
     * @param notify         通知回调
     * @return 是否之前已经成功回调
     */
    private Boolean updateOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
                                       PayOrderUnifiedRespDTO notify) {
        // 1. 判断 PayOrderDO 是否处于待支付
        PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
        if (order == null) {
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功，直接返回，不用重复更新
                && Objects.equals(order.getSuccessExtensionId(), orderExtension.getId())) {
            log.info("[updateOrderExtensionSuccess][order({}) 已经是已支付，无需更新]", order.getId());
            return true;
        }
        if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
        }

        // 2. 更新 PayOrderDO
        int updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
                PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus())
                        .channelId(channel.getId()).channelCode(channel.getCode())
                        .successTime(LocalDateTimeUtil.parse(notify.getSuccessTime(), NORM_DATETIME_PATTERN))
                        .successExtensionId(orderExtension.getId()).no(orderExtension.getNo())
                        .channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId())
                        .channelFeeRate(new BigDecimal(channel.getFeeRate()))
                        .channelFeeAmount(MoneyUtils.calculateRatePrice(order.getAmount(), Double.valueOf(channel.getFeeRate())).longValue())
                        .build());
        if (updateCounts == 0) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
        }
        log.info("[updateOrderExtensionSuccess][order({}) 更新为已支付]", order.getId());
        return false;
    }

    private void notifyOrderClosed(PayChannelDO channel, PayOrderUnifiedRespDTO notify) {
        updateOrderExtensionClosed(channel, notify);
    }

    private void updateOrderExtensionClosed(PayChannelDO channel, PayOrderUnifiedRespDTO notify) {
        // 1. 查询 PayOrderExtensionDO
        PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getTradeNo());
        if (orderExtension == null) {
            throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
        }
        if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { // 如果已经是关闭，直接返回，不用重复更新
            log.info("[updateOrderExtensionClosed][orderExtension({}) 已经是支付关闭，无需更新]", orderExtension.getId());
            return;
        }
        // 一般出现先是支付成功，然后支付关闭，都是全部退款导致关闭的场景。这个情况，我们不更新支付拓展单，只通过退款流程，更新支付单
        if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) {
            log.info("[updateOrderExtensionClosed][orderExtension({}) 是已支付，无需更新为支付关闭]", orderExtension.getId());
            return;
        }
        if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
        }

        // 2. 更新 PayOrderExtensionDO
        int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(),
                PayOrderExtensionDO.builder().status(PayOrderStatusEnum.CLOSED.getStatus()).channelNotifyData(toJsonString(notify))
                        .build());
        if (updateCounts == 0) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
        }
        log.info("[updateOrderExtensionClosed][orderExtension({}) 更新为支付关闭]", orderExtension.getId());
    }


    @Override
    public void updateOrderRefundPrice(BigInteger id, Integer incrRefundPrice) {
        PayOrderDO order = orderMapper.selectById(id);
        if (order == null) {
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) {
            throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR);
        }
        if (order.getRefundAmount() + incrRefundPrice > order.getAmount()) {
            throw exception(PAY_REFUND_AMOUNT_EXCEED);
        }

        // 更新订单
        PayOrderDO updateObj = new PayOrderDO()
                .setRefundAmount(order.getRefundAmount() + incrRefundPrice)
                .setStatus(PayOrderStatusEnum.REFUND.getStatus());
        int updateCount = orderMapper.updateByIdAndStatus(id, order.getStatus(), updateObj);
        if (updateCount == 0) {
            throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR);
        }
    }

    @Override
    public PayOrderDO getOrderByMerchantOrderId(String merchantOrderId) {
        return orderMapper.selectByMerchantOrderId(merchantOrderId);
    }

    /**
     * 通过pay_order_extension的支付订单号查询订单信息
     * @param no 支付订单号
     * @return
     */
    @Override
    public PayOrderDO getByTradeNo(String no){
        return orderMapper.selectByTradeNo(no);
    }

    /**
     * 更新 PayOrderExtensionDO 支付成功
     *
     * @param no        支付订单号（支付模块）
     * @param rawNotify 通知数据
     * @return PayOrderExtensionDO 对象
     */
    private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, PayNotifyReqDTO rawNotify) {
        // 1.1 查询 PayOrderExtensionDO
        PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(no);
        if (orderExtension == null) {
            throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_NOT_FOUND);
        }
        if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态，必须是待支付
            throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
        }
        // 1.2 更新 PayOrderExtensionDO
        int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(),
                PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId())
                        .status(PayOrderStatusEnum.SUCCESS.getStatus())
                        .channelNotifyData(toJsonString(rawNotify)).build());
        if (updateCounts == 0) { // 校验状态，必须是待支付
            throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
        }
        log.info("[updatePayOrderSuccess][支付拓展单({}) 更新为已支付]", orderExtension.getId());
        return orderExtension;
    }

    /**
     * 更新 PayOrderDO 支付成功
     *
     * @param channel        支付渠道
     * @param orderExtension 支付拓展单
     * @param notify         通知回调
     * @return PayOrderDO 对象
     */
    private PayOrderDO updatePayOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
                                             PayOrderNotifyRespDTO notify) {
        // 2.1 判断 PayOrderDO 是否处于待支付
        PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
        if (order == null) {
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态，必须是待支付
            throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
        }
        // 2.2 更新 PayOrderDO
        int updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
                PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus())
                        .channelId(channel.getId()).channelCode(channel.getCode())
                        .successTime(notify.getSuccessTime()).successExtensionId(orderExtension.getId())
                        .channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId())
                        .notifyTime(LocalDateTime.now()).build());
        if (updateCounts == 0) { // 校验状态，必须是待支付
            throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
        }
        log.info("[updatePayOrderSuccess][支付订单({}) 更新为已支付]", order.getId());
        return order;
    }


    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private PayOrderServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

}
